package org.desktop.diskutils.business.categories.factory.impl;

import java.awt.Color;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.desktop.diskutils.business.categories.IFileCategory;
import org.desktop.diskutils.business.categories.factory.IFileCategoryDefFactory;
import org.desktop.diskutils.business.categories.impl.FileCategory;
import org.desktop.diskutils.business.categories.impl.FileCategoryEntity;
import org.desktop.diskutils.business.categories.impl.FileType;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class XMLCategoryDefFactory implements IFileCategoryDefFactory {

	private static final String EXTENSION_GROUP_TAG = "extension-group";
	private static final String FILE_EXTENSION_TAG = "file-extension";

	private static final String NAME_ATTR = "name";
	private static final String COLOR_ATTR = "color";

	private static final Map<String, Integer> namedColors = new HashMap<>();
	static {
		namedColors.put("blue".toLowerCase(), Color.blue.getRGB());
		namedColors.put("cyan".toLowerCase(), Color.cyan.getRGB());
		namedColors.put("darkGray".toLowerCase(), Color.darkGray.getRGB());
		namedColors.put("gray".toLowerCase(), Color.gray.getRGB());
		namedColors.put("green".toLowerCase(), Color.green.getRGB());
		namedColors.put("lightGray".toLowerCase(), Color.lightGray.getRGB());
		namedColors.put("magenta".toLowerCase(), Color.magenta.getRGB());
		namedColors.put("orange".toLowerCase(), Color.orange.getRGB());
		namedColors.put("pink".toLowerCase(), Color.pink.getRGB());
		namedColors.put("red".toLowerCase(), Color.red.getRGB());
		namedColors.put("white".toLowerCase(), Color.white.getRGB());
		namedColors.put("yellow".toLowerCase(), Color.yellow.getRGB());
	}

	@Override
	public IFileCategory build(final InputStream configDescription) {
		SAXParserFactory factory = SAXParserFactory.newInstance();
		FileCategory rootCategory = null;
		try {
			SAXParser saxParser = factory.newSAXParser();
			XMLConfigHandler handler = new XMLConfigHandler();
			saxParser.parse(configDescription, handler);

			rootCategory = handler.getRootCategory();

			// If the root directory does not have a color - set one
			if (rootCategory.getColor() == null) {
				rootCategory.setColor(namedColors.get("lightGray".toLowerCase()));
			}

			generateMissingColors(rootCategory, 1);
		} catch (ParserConfigurationException | SAXException | IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return rootCategory;
	}

	private void generateMissingColors(final FileCategory rootCategory, final int depth) {
		Color c = new Color(rootCategory.getColor());
		int r = c.getRed();
		int g = c.getGreen();
		int b = c.getBlue();

		int numOfChildren = rootCategory.getChildren().size();
		double range = 256 / depth;

		r = (int) Math.max(0, r - range / 2);
		g = (int) Math.max(0, r - range / 2);
		b = (int) Math.max(0, r - range / 2);

		double step = range / (numOfChildren + 1);
		int i = 1;
		// Set colors for all children that do not have
		for (FileCategoryEntity child : rootCategory.getChildren()) {
			if (child.getColor() == null) {
				int newR = (int) (r + step * i) % 255;
				int newG = (int) (g + step * i) % 255;
				int newB = (int) (b + step * i) % 255;
				child.setColor(new Color(newR, newG, newB).getRGB());
				i++;
			}
		}

		for (FileCategoryEntity child : rootCategory.getChildren()) {
			if (child instanceof FileCategory) {
				generateMissingColors((FileCategory) child, depth + 1);
			}
		}
	}

	private static class XMLConfigHandler extends DefaultHandler {
		private FileCategory rootCategory;
		private Stack<FileCategory> categories;

		public XMLConfigHandler() {
			categories = new Stack<>();
		}

		@Override
		public void startElement(final String uri, final String localName, final String qName,
				final Attributes attributes) throws SAXException {
			switch (qName) {
			case EXTENSION_GROUP_TAG:
				String groupName = attributes.getValue(NAME_ATTR);
				Integer groupColor = parseColor(attributes.getValue(COLOR_ATTR));
				FileCategory category = new FileCategory(groupColor, groupName);

				if (rootCategory == null) {
					assert categories.isEmpty();
					rootCategory = category;
				} else {
					categories.peek().addChild(category);
				}
				categories.push(category);

				break;
			case FILE_EXTENSION_TAG:
				String extensionName = parseExtension(attributes.getValue(NAME_ATTR));
				Integer extensionsColor = parseColor(attributes.getValue(COLOR_ATTR));
				FileType fileType = new FileType(extensionsColor, extensionName);
				categories.peek().addChild(fileType);

				break;
			default:
				throw new IllegalArgumentException("Invalid tag " + qName);
			}

		}

		@Override
		public void endElement(final String uri, final String localName, final String qName) throws SAXException {
			if (EXTENSION_GROUP_TAG.equals(qName)) {
				categories.pop();
			}
		}

		public FileCategory getRootCategory() {
			return rootCategory;
		}

		private static Integer parseColor(final String color) {
			if (color != null) {
				String lowercaseColor = color.trim().toLowerCase();
				if (namedColors.containsKey(lowercaseColor)) {
					return namedColors.get(lowercaseColor);
				} else {
					return Integer.parseInt(lowercaseColor);
				}
			} else {
				return null;
			}
		}

		private static String parseExtension(final String ext) {
			return ext.trim().toLowerCase().intern();
		}
	}

}
